package com.netflix.appinfo;

import com.google.inject.ProvidedBy;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import javax.inject.Singleton;

@Slf4j
@Data
@Singleton
@ProvidedBy(CloudInstanceConfigProvider.class)
public class CloudInstanceConfig extends PropertiesInstanceConfig implements RefreshableInstanceConfig {

    private final RefreshableAmazonInfoProvider amazonInfoHolder;

    private static final String[] DEFAULT_AWS_ADDRESS_RESOLUTION_ORDER = new String[]{
            AmazonInfo.MetaDataKey.publicHostname.name(),
            AmazonInfo.MetaDataKey.localIpv4.name()
    };

    public CloudInstanceConfig() {
        this("eureka");
    }

    public CloudInstanceConfig(String namespace) {
        this(namespace, new Archaius1AmazonInfoConfig(namespace), null, true);
    }

    //@Override
    //public String resolveDefaultAddress(boolean refresh) {
    //    return null;
    //}

    @Deprecated
    /* visible for testing */ static boolean shouldUpdate(AmazonInfo newInfo, AmazonInfo oldInfo) {
        return RefreshableAmazonInfoProvider.shouldUpdate(newInfo, oldInfo);
    }


    /* visible for testing */ CloudInstanceConfig(AmazonInfo info) {
        this(CommonConstants.DEFAULT_CONFIG_NAMESPACE, new Archaius1AmazonInfoConfig(CommonConstants.DEFAULT_CONFIG_NAMESPACE), info, false);
    }

    /* visible for testing */ CloudInstanceConfig(String namespace, RefreshableAmazonInfoProvider refreshableAmazonInfoProvider) {
        super(namespace);
        this.amazonInfoHolder = refreshableAmazonInfoProvider;
    }

    /* visible for testing */ CloudInstanceConfig(String namespace, AmazonInfoConfig amazonInfoConfig, AmazonInfo initialInfo, boolean eagerInit) {
        super(namespace);
        if (eagerInit) {
            RefreshableAmazonInfoProvider.FallbackAddressProvider fallbackAddressProvider =
                    new RefreshableAmazonInfoProvider.FallbackAddressProvider() {
                        @Override
                        public String getFallbackIp() {
                            return CloudInstanceConfig.super.getIpAddress();
                        }

                        @Override
                        public String getFallbackHostname() {
                            return CloudInstanceConfig.super.getHostName(false);
                        }
                    };
            this.amazonInfoHolder = new RefreshableAmazonInfoProvider(amazonInfoConfig, fallbackAddressProvider);
        } else {
            this.amazonInfoHolder = new RefreshableAmazonInfoProvider(initialInfo, amazonInfoConfig);
        }
    }

    @Deprecated
    public String resolveDefaultAddress() {
        return this.resolveDefaultAddress(true);
    }

    @Override
    public String resolveDefaultAddress(boolean refresh) {
        // In this method invocation data center info will be refreshed.
        String result = getHostName(refresh);

        for (String name : getDefaultAddressResolutionOrder()) {
            try {
                AmazonInfo.MetaDataKey key = AmazonInfo.MetaDataKey.valueOf(name);
                String address = amazonInfoHolder.get().get(key);
                if (address != null && !address.isEmpty()) {
                    result = address;
                    break;
                }
            } catch (Exception e) {
                log.error("failed to resolve default address for key {}, skipping", name, e);
            }
        }

        return result;
    }

    @Override
    public String getHostName(boolean refresh) {
        if (refresh) {
            amazonInfoHolder.refresh();
        }
        return amazonInfoHolder.get().get(AmazonInfo.MetaDataKey.publicHostname);
    }

    @Override
    public String getIpAddress() {
        return this.shouldBroadcastPublicIpv4Addr() ? getPublicIpv4Addr() : getPrivateIpv4Addr();
    }

    private String getPrivateIpv4Addr() {
        String privateIpv4Addr = amazonInfoHolder.get().get(AmazonInfo.MetaDataKey.localIpv4);
        return privateIpv4Addr == null ? super.getIpAddress() : privateIpv4Addr;
    }

    private String getPublicIpv4Addr() {
        String publicIpv4Addr = amazonInfoHolder.get().get(AmazonInfo.MetaDataKey.publicIpv4);
        return publicIpv4Addr == null ? super.getIpAddress() : publicIpv4Addr;
    }

    @Override
    public DataCenterInfo getDataCenterInfo() {
        return amazonInfoHolder.get();
    }

    @Override
    public String[] getDefaultAddressResolutionOrder() {
        String[] order = super.getDefaultAddressResolutionOrder();
        return (order.length == 0) ? DEFAULT_AWS_ADDRESS_RESOLUTION_ORDER : order;
    }

    @Deprecated
    public synchronized void refreshAmazonInfo() {
        amazonInfoHolder.refresh();
    }
}
